package com.sz.biz.logistics.serialno.algorithm.impl;

import com.sz.biz.logistics.serialno.algorithm.VerificationCodeAlgorithm;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants;
import com.sz.biz.logistics.serialno.constants.BillNumberErrorCodes;
import com.sz.biz.logistics.serialno.dto.AlgorithmInfoDto;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.utils.StringUtils;

import javax.annotation.Nullable;

/**
 * Package: com.sz.biz.logistics.channelno.algorithm<br>
 * Description: 运单号校验位算法<br>
 * @author WuHan<br>
 * @date 2017-12-14 13:59<br>
 * @serial
 * @since sys-1.1.3
 */
public class WaybillNumberAlgorithm implements VerificationCodeAlgorithm {
    
    /**
     * 算法信息DTO
     */
    private static AlgorithmInfoDto algorithmInfoDto = new AlgorithmInfoDto();
    
    private static final int VALIDATE_LENGTH = 6;
    
    private static final int VERIFYCODE_INDEX = 6;
    
    private static final int PREFIX_LENGTH = 3;
    
    private static final int[] WEIGHT = {1, 6, 3, 4, 5, 2};
    
    //DTO初始化
    static {
        algorithmInfoDto.setCode(BillNumberConstants.AlgorithmInfo.WAYBILL_NUMBER_ALGORITHM);
        algorithmInfoDto.setName("运单号生成算法");
        algorithmInfoDto.setValidateLength(VALIDATE_LENGTH);
        algorithmInfoDto.setVerifyCodeIndex(VERIFYCODE_INDEX);
        algorithmInfoDto.setExample("12345*?");
        algorithmInfoDto.setType(BillNumberConstants.NumberType.WAYBILL_NUMBER);
    }
    
    /**
     * 返回算法信息供工厂方法调用
     * @return 算法信息
     */
    public static AlgorithmInfoDto getAlgorithmInfoDto() {
        return algorithmInfoDto;
    }
    
    public static int getValidateLength() {
        
        return VALIDATE_LENGTH;
    }
    
    @Override
    public void getAlgorithmInfo() {
    
    }
    
    /**
     *
     * @param rawString 原始串号,接受"123456789?"类型的9位数字序列+1位校验位通配符的格式
     * @param pattern 在本算法中为正则表达式
     * @param formatRule 格式规则。
     * @return 校验位数字
     */
    @Override
    public String generateVerificationCode(String rawString, @Nullable String pattern, String formatRule) {
        if(StringUtils.isEmpty(formatRule)){
            throw new RuntimeException("formatRule 为空");
        }
        formatRule = formatRule.replaceAll(BillNumberConstants.Characters.BLANK, BillNumberConstants.Characters.EMPTY_STRING);
        formatCheck(rawString, pattern, formatRule);
        String temp = rawString.substring(PREFIX_LENGTH, VALIDATE_LENGTH + PREFIX_LENGTH);
        return rawString.replace(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE, getVerifyCode(temp));

    }
    
    private void formatCheck(String rawString, @Nullable String pattern, String formatRule) {
        if (StringUtils.isEmpty(rawString) || StringUtils.isEmpty(pattern) || StringUtils.isEmpty(formatRule)) {
            throw Exceptions.bizException(BillNumberErrorCodes.ERROR_WAYBILL_NUMBER_EMPTY_ARGUMENT);
        }
        if (VALIDATE_LENGTH + PREFIX_LENGTH + 1 != rawString.length()) {
            throw Exceptions.bizException(BillNumberErrorCodes.ERROR_WAYBILL_NUMBER_WRONG_LENGTH);
        }
        if (rawString.length() != formatRule.replaceAll(BillNumberConstants.Characters.BLANK, BillNumberConstants.Characters.EMPTY_STRING).length()) {
            throw Exceptions.bizException(BillNumberErrorCodes.ERROR_WAYBILL_NUMBER_PATTERN_LENGTH_MISMATCH);
        }
        if (!rawString.contains(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
            throw Exceptions.bizException(BillNumberErrorCodes.ERROR_WAYBILL_NUMBER_NOT_CONTAIN_VERIFYCODE);
        }
        if (!rawString.substring(VALIDATE_LENGTH + PREFIX_LENGTH, VALIDATE_LENGTH + PREFIX_LENGTH + 1).equals(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
            throw Exceptions.bizException(BillNumberErrorCodes.ERROR_WAYBILL_NUMBER_VERIFYCODE_DISLOCATE);
        }
    }
    
    /**
     *
     * @param channelNumber 待验的单号,如"ABB01234565C"
     * @param pattern 在本算法中为正则表达式
     * @param formatRule 格式规则，当模式串形如 ABC123456?D-ABC234567?D 时，<br>无法判断某一位置具体是纯数字/纯字母还是混合类型，<br>为防止范围计算出错，加入基础原则进行辅助判断。
     * @return 是否符合校验规则
     */
    @Override
    public boolean validityCheck(String channelNumber, String pattern, String formatRule) {
        int index = PREFIX_LENGTH + VALIDATE_LENGTH;
        String temp = channelNumber.substring(0, index)
            + BillNumberConstants.Symbols.SYMBOL_VERIFYCODE
            + channelNumber.substring(index + 1);
        formatCheck(temp, pattern, formatRule);
        return temp.replace(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE,
                            getVerifyCode(temp.substring(index - VALIDATE_LENGTH, index)))
                   .equals(channelNumber);
    }

    @Override
    public boolean checkSerialNumber(String serialNumber) {
        return !StringUtils.isEmpty(serialNumber) && this.checkValidityLength(serialNumber, VALIDATE_LENGTH);
    }
    
    
    /**
     * 校验位生成（按位加权求和加位数取余(mod 10)算法，若某位为0，则该位数值按（位置序号（0开始）+ 串长 - 串中0的个数）参与计算）
     * @param pattern 传入的模式串，作为生成校验位的母串
     * @param length 模式串长度，作为合理性判据
     * @param weight 按模式串位数存储的权值数组
     * @return [0,9]之间的整数
     * */
    private static String getVerifyCode(String pattern, int length, int[] weight
    ) {
        int sum = 0;
            int[] singleDigit = new int[length];
            for (int i = 0; i < length; i++) {
                singleDigit[i] = pattern.charAt(i) - '0' == 0
                        ? (i + length) % 10
                        : pattern.charAt(i) - '0';
                sum += singleDigit[i] * weight[i];
            }
        return Integer.toString((sum + length) % 10).substring(0,1);
    }
    
    /**
     * 给定辅助条件的校验方法
     * @param pattern 不包含校验位和业务类型的9位序号。
     * @see #getVerifyCode(String, int, int[])
     * */
    private static String getVerifyCode(String pattern) {
        
        return getVerifyCode(pattern, VALIDATE_LENGTH, WEIGHT);
    }
    
}
